package com.yonyou.pmclouds.basecom.conf;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yonyou.pmclouds.basecom.exception.BusinessException;
import com.yonyou.pmclouds.basecom.runtime.RuntimeEnvironment;
import com.yonyou.pmclouds.basecom.util.HttpTookit;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 调用nc的配置
 */
@Configuration
@Slf4j
@EnableConfigurationProperties(NCInvokerConfiguration.NCInvokerProperties.class)
public class NCInvokerConfiguration {
    private static ObjectMapper mapper = new ObjectMapper();

    static {
        mapper.configure(JsonParser.Feature.ALLOW_MISSING_VALUES, true);
    }

    @Bean
    @ConditionalOnProperty("pmcloud.nc-invoker.nc-address")
    public Invoker ncDirectInvoker(NCInvokerProperties properties) {
        return new NCDirectInvoker(properties);
    }

    @Bean
    @ConditionalOnProperty("pmcloud.nc-invoker.ubl-address")
    public Invoker ncUBLInvoker(NCInvokerProperties properties) {
        return new NCUBLInvoker(properties);
    }

    @Bean
    @ConditionalOnMissingBean
    public Invoker mockeInvoker() {
        // TODO 增加一个mocker，这个mocker暂时想不好怎么写得平台化一点，就需要调用的时候，改代码吧😊
        return new Invoker() {
            @Override
            public Result invokeSync(NCServiceVO serviceVO,Map<String, Object> params, String tenant, String user) throws BusinessException {
                return null;
            }

            @Override
            public Future<Result> invokeAsync(NCServiceVO serviceVO,Map<String, Object> params, String tenant, String user, CallBack callBack) throws BusinessException {
                final Result result = this.invokeSync(serviceVO,params, tenant, user);
                if (callBack != null)
                    callBack.whenResult(result);
                return new Future<Result>() {
                    @Override
                    public boolean cancel(boolean mayInterruptIfRunning) {
                        return false;
                    }

                    @Override
                    public boolean isCancelled() {
                        return false;
                    }

                    @Override
                    public boolean isDone() {
                        return true;
                    }

                    @Override
                    public Result get() throws InterruptedException, ExecutionException {
                        return result;
                    }

                    @Override
                    public Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
                        return result;
                    }
                };
            }
        };
    }

    public interface Invoker {
        String DATA_SOURCE_CONST = "data_source";
        String GROUP_CONST = "pk_group";
        String SERVICE_NAME_CONST = "serviceName";

        Result invokeSync(NCServiceVO serviceVO, Map<String, Object> params, String tenant, String user) throws BusinessException;

        Future<Result> invokeAsync(NCServiceVO serviceVO, Map<String, Object> params, String tenant, String user, CallBack callBack) throws BusinessException;
    }

    public interface CallBack {
        void whenResult(Result result) throws BusinessException;
    }

    @Setter
    @Getter
    @ConfigurationProperties(prefix = "pmcloud.nc-invoker")
    public static class NCInvokerProperties {
        private String ncAddress;
        private String restAppId;
        private String ncDataSource;
        private String ncGroupId;
        private String ublAddress;
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Result {
        public static final String STATUS_OK = "ok";
        public static final String STATUS_INTERNAL_ERROR = "internalError";
        public static final String STATUS_FAILED = "failed";

        String status;
        String result;
        String errorCode;
    }

    public static class NCDirectInvoker implements Invoker {

        private ExecutorService ncThreadPool = new ThreadPoolExecutor(20,
                100,
                30L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(1000),
                new ThreadPoolExecutor.DiscardPolicy()); // 目前简单粗暴的拒绝，认为nc直调应该不会出现这么大的并发量
        private NCInvokerProperties properties;
        private String ncServiceUrl;

        NCDirectInvoker(NCInvokerProperties properties) {
            this.properties = properties;
            //this.ncServiceUrl = properties.ncAddress + NC_SERVICE_SERVLET_PATH;
        }

        @Override
        public Result invokeSync(NCServiceVO serviceVO,Map<String, Object> params, String tenant, String user) {
            //  集团及帐套
            params.put(DATA_SOURCE_CONST, properties.ncDataSource);
            params.put(GROUP_CONST, properties.ncGroupId);
            params.put(SERVICE_NAME_CONST, serviceVO.getServiceName());
            String result = null;
            ncServiceUrl = this.properties.getNcAddress() + serviceVO.getServicePath();
            log.info("nc直连调用url:{}，参数params:{}", ncServiceUrl, params);
            try {
                result = HttpTookit.doPostWithJson(ncServiceUrl, mapper.writeValueAsString(params), null);
            } catch (JsonProcessingException e) {
                log.error("nc invoke failed: serialization failed");
                return new Result(Result.STATUS_INTERNAL_ERROR, null, null);
            } catch (Exception e) {
                return new Result(Result.STATUS_FAILED, e.getMessage(), null);
            }
            if (result == null) {
                return new Result(Result.STATUS_OK, null, null);
            }
            String t = result;
            try {
                t = mapper.readValue(result, String.class);
            } catch (IOException e) {
                log.error("", e);
            }
            if (result.contains("responseCode")) {
                return new Result(Result.STATUS_FAILED, t, null);
            }
            return new Result(Result.STATUS_OK, t, null);
        }

        @Override
        public Future<Result> invokeAsync(final NCServiceVO serviceVO, Map<String, Object> params, String tenant, String user, final CallBack callBack) {
            // TODO 今后异步的实现是否要继续用线程池 值得思考
            return ncThreadPool.submit(new NCInvokerTask(serviceVO,params, tenant, user) {

                @Override
                public Result call() throws Exception {
                    Result result = NCDirectInvoker.this.invokeSync(serviceVO, this.getParams(), this.getTenant(), this.getUser());
                    try {
                        if (callBack != null) {
                            RuntimeEnvironment.setLoginUserId(this.getUser());
                            RuntimeEnvironment.setTenantId(this.getTenant());
                            callBack.whenResult(result);
                        }
                    } catch (Exception e) {
                        log.error("invoke nc service {} callback failed in tenant {}, error is {}", this.getServiceVO().getServiceName(), this.getTenant(), e);
                    }
                    return result;
                }
            });
        }
    }


    public static class NCUBLInvoker implements Invoker {
        public static final String UBL_SERVICE_SERVLET_PATH = "/gwmanage/ncopenapi/commonapi";
        //public static final String NC_UBL_SERVICE = "nc.impl.pim.pimpub.cloud.PMCloudDriveProjectStateImpl";
        public static final String NC_UBL_SERVICE_CONST = "itype";
        public static final String UBL_INFO_CONST = "info";
        public static final String UBL_STATUS_CONST = "status";
        public static final String UBL_ERROR_MSG_CONST = "errormsg";
        public static final String UBL_ERROR_CODE_CONST = "errorcode";
        public static final String UBL_DATA_CODE_CONST = "data";


        private ExecutorService ncThreadPool = new ThreadPoolExecutor(20,
                100,
                30L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(1000),
                new ThreadPoolExecutor.DiscardPolicy()); // 目前简单粗暴的拒绝，认为nc直调应该不会出现这么大的并发量
        private NCInvokerProperties properties;
        private String ublServiceUrl;

        NCUBLInvoker(NCInvokerProperties properties) {
            this.properties = properties;
            this.ublServiceUrl = properties.ublAddress + UBL_SERVICE_SERVLET_PATH;
        }

        @Override
        public Result invokeSync(NCServiceVO serviceVO,Map<String, Object> params, String tenant, String user) {
            //  集团及帐套
            params.put(SERVICE_NAME_CONST, serviceVO.getServiceName());
            String serviceUrl = this.ublServiceUrl + "?" + "tenantId=" + tenant + "&appid=" + properties.restAppId;
            log.info("访问友企连url:{}，参数params:{}", serviceUrl, params);
            String result = null;
            try {
                Map<String, String> ublParam = new HashMap<>();
                ublParam.put(UBL_INFO_CONST, mapper.writeValueAsString(params));
                String ncUblService = serviceVO.getServiceImpl();
                ublParam.put(NC_UBL_SERVICE_CONST, ncUblService);
                result = HttpTookit.doPostWithJson(serviceUrl, mapper.writeValueAsString(ublParam), null);
            } catch (JsonProcessingException e) {
                log.error("nc invoke failed: serialization failed");
                return new Result(Result.STATUS_INTERNAL_ERROR, null, null);
            } catch (Exception e) {
                return new Result(Result.STATUS_FAILED, e.getMessage(), null);
            }
            if (result == null) {
                return new Result(Result.STATUS_OK, null, null);
            }
            if (result.contains("responseCode")) {
                // TODO 这里调整一下，nc的异常返回页包含这个值
                return new Result(Result.STATUS_FAILED, result, null);
            }
            // 处理友企联的结果
            Map<String, Object> ublResult = null;
            try {
                ublResult = mapper.readValue(result, Map.class);
            } catch (IOException e) {
                return new Result(Result.STATUS_INTERNAL_ERROR, result, null);
            }
            if (ublResult == null) {
                return new Result(Result.STATUS_OK, result, null);
            }
            String status = (String) ublResult.get(UBL_STATUS_CONST);
            if (!"success".equals(status)) {
                return new Result(Result.STATUS_FAILED, (String) ublResult.get(UBL_ERROR_MSG_CONST), (String) ublResult.get(UBL_ERROR_CODE_CONST));
            }
            return new Result(Result.STATUS_OK, (String) ublResult.get(UBL_DATA_CODE_CONST), null);
        }


        @Override
        public Future<Result> invokeAsync(NCServiceVO serviceVO,Map<String, Object> params, String tenant, String user, final CallBack callBack) {
            // TODO 整个线程池？有点不太想搞线程池了，已经有两个了。。。
            return ncThreadPool.submit(new NCInvokerTask(serviceVO,params, tenant, user) {

                @Override
                public Result call() throws Exception {
                    Result result = NCUBLInvoker.this.invokeSync(this.getServiceVO(), this.getParams(), this.getTenant(), this.getUser());
                    try {
                        if (callBack != null) {
                            RuntimeEnvironment.setLoginUserId(this.getUser());
                            RuntimeEnvironment.setTenantId(this.getTenant());
                            callBack.whenResult(result);
                        }
                    } catch (Exception e) {
                        log.error("invoke nc service {} callback failed in tenant {}, error is {}", this.getServiceVO().getServiceName(), this.getTenant(), e);
                    }
                    return result;
                }
            });
        }
    }

    @Getter
    @AllArgsConstructor
    abstract static class NCInvokerTask implements Callable<Result> {
        private NCServiceVO serviceVO;
        private Map<String, Object> params;
        private String tenant;
        private String user;
    }

    public static class RejectHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // TODO 完善拒绝逻辑
            log.error("");
        }
    }
}
